import tar from 'tar-stream'
import { computeVmdkLength, vhdToVMDKIterator } from '.'
import { fromCallback } from 'promise-toolbox'
import { pipeline } from 'stream'

// WE MIGHT WANT TO HAVE A LOOK HERE: https://opennodecloud.com/howto/2013/12/25/howto-ON-ovf-reference.html

/**
 *
 * @param outStream
 * @param vmName
 * @param vmDescription
 * @param disks [{name, fileName, capacityMB, getStream}]
 * @param nics [{name, networkName}]
 * @param vmMemoryMB
 * @param cpuCount
 * @returns readStream
 */
export async function writeOvaOn(
  outStream,
  { vmName, vmDescription = '', disks = [], firmware = 'bios', nics = [], vmMemoryMB = 64, cpuCount = 1 }
) {
  const ovf = createOvf(vmName, vmDescription, disks, nics, vmMemoryMB, cpuCount, firmware)
  const tarStream = tar.pack()
  pipeline(tarStream, outStream, () => {})
  await fromCallback.call(tarStream, tarStream.entry, { name: `metadata.ovf` }, Buffer.from(ovf, 'utf8'))

  // https://github.com/mafintosh/tar-stream/issues/24#issuecomment-558358268
  async function pushDisk(disk) {
    let { iterator, size } = await vhdToVMDKIterator(disk.name, await disk.getStream())
    if (size === undefined) {
      size = await computeVmdkLength(disk.name, await disk.getStream())
    }
    disk.fileSize = size
    return new Promise((resolve, reject) => {
      const entryWriteStream = tarStream.entry({ name: `${disk.name}.vmdk`, size, type: 'file' }, err => {
        if (err == null) {
          return resolve()
        } else return reject(err)
      })
      pipeline(iterator, entryWriteStream, () => {})
    })
  }

  for (const disk of disks) {
    await pushDisk(disk)
  }
  tarStream.finalize()
  return outStream
}

function createDiskSections(disks) {
  const fileReferences = []
  const diskFragments = []
  const diskItems = []
  for (let i = 0; i < disks.length; i++) {
    const disk = disks[i]
    const diskId = `vmdisk${i + 1}`
    fileReferences.push(`    <File ovf:href="${disk.fileName}" ovf:id="file${i + 1}"/>`)
    diskFragments.push(
      `    <Disk ovf:capacity="${
        disk.capacityMB
      }" ovf:capacityAllocationUnits="byte * 2^20" ovf:diskId="${diskId}" ovf:fileRef="file${
        i + 1
      }" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" />`
    )
    diskItems.push(`
      <Item>
        <rasd:AddressOnParent>${i}</rasd:AddressOnParent>
        <rasd:ElementName>Hard Disk ${i + 1}</rasd:ElementName>
        <rasd:HostResource>ovf:/disk/${diskId}</rasd:HostResource>
        <rasd:InstanceID>${diskId}</rasd:InstanceID>
        <rasd:Parent>4</rasd:Parent>
        <rasd:ResourceType>17</rasd:ResourceType>
      </Item>
    `)
  }
  return {
    fileReferences: fileReferences.join('\n'),
    diskFragments: diskFragments.join('\n'),
    diskItems: diskItems.join('\n'),
  }
}

function createNicsSection(nics) {
  const networks = new Set()
  const nicItems = []
  for (let i = 0; i < nics.length; i++) {
    const nic = nics[i]
    networks.add(nic.networkName)
    const text = `
      <Item>
        <rasd:AddressOnParent>${i}</rasd:AddressOnParent>
        <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
        <rasd:Connection>${nic.networkName}</rasd:Connection>
        <rasd:Description>PCNet32 ethernet adapter on "${nic.networkName}"</rasd:Description>
        <rasd:ElementName>Connection to ${nic.networkName}</rasd:ElementName>
        <rasd:InstanceID>${'nic' + i}</rasd:InstanceID>
        <rasd:ResourceSubType>PCNet32</rasd:ResourceSubType>
        <rasd:ResourceType>10</rasd:ResourceType>
      </Item>
`
    nicItems.push(text)
  }
  const networksElements = []
  for (const network of networks) {
    networksElements.push(`    <Network ovf:name="${network}"/>`)
  }
  return { nicsSection: nicItems.join('\n'), networksSection: networksElements.join('\n') }
}

function createOvf(vmName, vmDescription, disks = [], nics = [], vmMemoryMB = 64, cpuCount = 1, firmware = 'bios') {
  const diskSection = createDiskSections(disks)
  const networkSection = createNicsSection(nics)
  let id = 1
  const nextId = () => id++

  return `<?xml version="1.0" encoding="UTF-8"?>
<!--Generated by Xen Orchestra-->
<ovf:Envelope xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
    xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
    xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData">
  <References>
${diskSection.fileReferences}
  </References>
  <DiskSection>
    <Info>Virtual disk information</Info>
${diskSection.diskFragments}
  </DiskSection>
  <NetworkSection>
    <Info>The list of logical networks</Info>
${networkSection.networksSection}
  </NetworkSection>
  <VirtualSystem ovf:id="${vmName}">
    <Info>A virtual machine</Info>
    <Name>${vmName}</Name>
    <OperatingSystemSection ovf:id="${nextId()}">
      <Info>The kind of installed guest operating system</Info>
   </OperatingSystemSection>
    <VirtualHardwareSection>
      <Info>Virtual hardware requirements</Info>
      <System>
        <vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
        <vssd:InstanceID>0</vssd:InstanceID>
        <vssd:VirtualSystemIdentifier>${vmName}</vssd:VirtualSystemIdentifier>
        <vssd:VirtualSystemType>vmx-11</vssd:VirtualSystemType>
      </System>
      <Item>
        <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
        <rasd:Description>Number of Virtual CPUs</rasd:Description>
        <rasd:ElementName>${cpuCount} virtual CPU(s)</rasd:ElementName>
        <rasd:InstanceID>1</rasd:InstanceID>
        <rasd:ResourceType>3</rasd:ResourceType>
        <rasd:VirtualQuantity>${cpuCount}</rasd:VirtualQuantity>
      </Item>
      <Item>
        <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
        <rasd:Description>Memory Size</rasd:Description>
        <rasd:ElementName>${vmMemoryMB}MB of memory</rasd:ElementName>
        <rasd:InstanceID>2</rasd:InstanceID>
        <rasd:ResourceType>4</rasd:ResourceType>
        <rasd:VirtualQuantity>${vmMemoryMB}</rasd:VirtualQuantity>
      </Item>
      <Item>
        <rasd:Address>0</rasd:Address>
        <rasd:Description>IDE Controller</rasd:Description>
        <rasd:ElementName>VirtualIDEController 0</rasd:ElementName>
        <rasd:InstanceID>4</rasd:InstanceID>
        <rasd:ResourceType>5</rasd:ResourceType>
      </Item>
      <Item ovf:required="false">
        <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
        <rasd:ElementName>VirtualVideoCard</rasd:ElementName>
        <rasd:InstanceID>5</rasd:InstanceID>
        <rasd:ResourceType>24</rasd:ResourceType>
      </Item>
${diskSection.diskItems}
${networkSection.nicsSection}
    </VirtualHardwareSection>
    <AnnotationSection ovf:required="false">
      <Info>A human-readable annotation</Info>
      <Annotation>${vmDescription}</Annotation>
    </AnnotationSection>
  </VirtualSystem>
</ovf:Envelope>`
}
